package bean;

import frame.service.anno.*;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.*;

@Component
public class Bean implements InitializingBean, ApplicationContextAware {
    private ApiDocMD apiDoc;
    private ApplicationContext applicationContext;
    private List<ApiGroup> apis = new ArrayList();

    @Override
    public void afterPropertiesSet() throws Exception {
        Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(MiayService.class);
        Iterator<Map.Entry<String, Object>> iterator = beansWithAnnotation.entrySet().iterator();
        while(iterator.hasNext()){
            Object object = iterator.next().getValue();
            apis.add(createApiGroup(object));
        }
        apiDoc.createDoc(apis);
    }

    private ApiGroup createApiGroup(Object object){
        MiayService annotation = object.getClass().getAnnotation(MiayService.class);
        ApiGroup apiGroup = new ApiGroup();
        apiGroup.setSafeMode(annotation.safeMode());
        apiGroup.setApiModule(annotation.module());
        apiGroup.setApiService(annotation.service());
        apiGroup.setSupportHttpMethod(annotation.supportHttpMethod());

        LinkedHashMap<String, LinkedHashMap<String, ApiGroup.Api>> apiActions = apiGroup.getApiAction();

        Method[] declaredMethods = object.getClass().getDeclaredMethods();
        for(Method method: declaredMethods){
            MiayAction actionAnn = method.getAnnotation(MiayAction.class);
            if(actionAnn!=null){
                String actionRegName = method.getName();
                if(!"".equals(actionAnn.name())){
                    actionRegName = actionAnn.name();
                }
                LinkedHashMap<String, ApiGroup.Api> actions = apiActions.get(actionRegName)!=null?apiActions.get(actionRegName):new LinkedHashMap<>();
                ApiGroup.Api api = createApi(method);
                api.setAction(actionRegName);
                api.setComment(actionAnn.comment());
                api.setVersion(actionAnn.version());

                ActionThrows actionThrows = method.getAnnotation(ActionThrows.class);
                if(actionThrows!=null){
                    for (String code: actionThrows.value()){
                        api.getReturnCode().put(code, ErrorCodeMapping.getErrorMsg(code));
                    }
                }

                actions.put(actionAnn.version(), api);
                apiActions.put(actionRegName, actions);
            }
        }

        return apiGroup;
    }

    private ApiGroup.Api createApi(Method method){
        ApiGroup.Api api = new ApiGroup.Api();
        ActionParam[] actionParams = method.getAnnotationsByType(ActionParam.class);
        for(ActionParam actionParam: actionParams){
            ApiGroup.Param param = new ApiGroup.Param();
            param.setName(actionParam.name());
            param.setComment(actionParam.comment());
            param.setRequired(actionParam.required());
            param.setUse(actionParam.use());
            if(actionParam.type()==ActionParam.Type.Request){
                api.addRequestParam(param);
            }else{
                api.addResponseParam(param);
            }
        }

        MiayActionRequest miayActionRequest = method.getAnnotation(MiayActionRequest.class);
        if(miayActionRequest!=null){
            Class value = miayActionRequest.value();
            try {
                BeanInfo beanInfo = Introspector.getBeanInfo(value);
                PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
                for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                    Method writeMethod = propertyDescriptor.getWriteMethod();
                    if(writeMethod==null){
                        continue;
                    }
                    MiayActionParam annotation = writeMethod.getAnnotation(MiayActionParam.class);
                    if(annotation!=null && !annotation.ignore()){
                        ApiGroup.Param param = new ApiGroup.Param();
                        param.setName("".equals(annotation.value())?propertyDescriptor.getName():annotation.value());
                        param.setComment(annotation.comment());
                        param.setRequired(annotation.required());
                        param.setUse(annotation.use());
                        api.addRequestParam(param);
                    }
                }
            } catch (IntrospectionException e) {
                e.printStackTrace();
            }
        }

        MiayActionResponse miayActionResponse = method.getAnnotation(MiayActionResponse.class);
        if(miayActionResponse!=null){
            Class value = miayActionResponse.value();
            try {
                BeanInfo beanInfo = Introspector.getBeanInfo(value);
                PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
                for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                    Method writeMethod = propertyDescriptor.getWriteMethod();
                    if(writeMethod==null){
                        continue;
                    }
                    MiayActionParam annotation = writeMethod.getAnnotation(MiayActionParam.class);
                    if(annotation!=null && !annotation.ignore()){
                        ApiGroup.Param param = new ApiGroup.Param();
                        param.setName("".equals(annotation.value())?propertyDescriptor.getName():annotation.value());
                        param.setComment(annotation.comment());
                        param.setRequired(annotation.required());
                        param.setUse(annotation.use());
                        api.addResponseParam(param);
                    }
                }
            } catch (IntrospectionException e) {
                e.printStackTrace();
            }
        }
        return api;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Autowired
    public void setApiDoc(ApiDocMD apiDoc){
        this.apiDoc = apiDoc;
    }
}
